package org.blankie.util;

import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.logging.Logger;
import java.util.logging.Level;

import org.blankie.lang.Validate;

public class ComponentStateHolder implements ComponentLifeCycle, Serializable, Cloneable {

    public interface ComponentLifeCycleHooks {
        
        void doInit() throws Exception;

        void doStart() throws Exception;

        void doStop() throws Exception;

        void doDestroy() throws Exception;

    }
    
    public static abstract class AbstractLifeCycleHooks<H>
        implements ComponentLifeCycleHooks, Serializable, Cloneable {

        protected final H holder;

        protected AbstractLifeCycleHooks(H holder) {
            Validate.parameterNotNullInstanceOf("holder", holder, Serializable.class, Cloneable.class);
            this.holder = holder;
        }

        public Object clone() {
            try {
                return super.clone();
            } catch (CloneNotSupportedException e) {
                throw new InternalError(e.getMessage());
            }
        }

    }

    private class DefaultLifeCycleHooks extends AbstractLifeCycleHooks<ComponentStateHolder> {

        DefaultLifeCycleHooks() {
            super(ComponentStateHolder.this);
        }

        public void doInit() throws Exception {
            holder.doInit();
        }

        public void doStart() throws Exception {
            holder.doStart();
        }

        public void doStop() throws Exception {
            holder.doStop();
        }

        public void doDestroy() throws Exception {
            holder.doDestroy();
        }

    }

    private static final Logger logger = Logger.getLogger(ComponentStateHolder.class.getName());

    private static final long serialVersionUID = 0L;

    private ComponentLifeCycleHooks hooks;

    private ComponentState state;

    public ComponentStateHolder() {
        this.hooks = new DefaultLifeCycleHooks();
        this.state = ComponentState.CREATED;
    }

    public ComponentStateHolder(ComponentLifeCycleHooks hooks) {
        Validate.parameterNotNull("hooks", hooks);
        this.hooks = hooks;
        this.state = ComponentState.CREATED;
    }

    public boolean isInitialized() {
        return state.after(ComponentState.INITIALIZED);
    }

    public boolean isStarted() {
        return state.after(ComponentState.STARTED);
    }

    public boolean isStopped() {
        return state.after(ComponentState.STOPPED);
    }

    public boolean isDestroyed() {
        return !state.before(ComponentState.DESTROYED);
    }

    public final boolean init() {
        if (isDestroyed()) {
            return false;
        } else if (isInitialized()) {
            return true;
        }

        boolean initialized = true;

        try {
            hooks.doInit();
            state = ComponentState.INITIALIZED;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Component failed to initialize", e);
            initialized = false;
        } 

        if (!initialized) {
            try {
                hooks.doDestroy();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to destroy as well", e);
            }

            state = ComponentState.DESTROYED;
        }

        assert ((state == ComponentState.INITIALIZED) == initialized);
        assert ((state == ComponentState.DESTROYED) == !initialized);
        return initialized;
    }

    public final boolean start() {
        if (!init()) {
            return false;
        } else if (getState() == ComponentState.STARTED) {
            return true;
        }

        boolean started = true;

        try {
            hooks.doStart();
            state = ComponentState.STARTED;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Component failed to start", e);
            started = false;
        }

        if (!started) {
            try {
                hooks.doStop();
                state = ComponentState.STOPPED;
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to stop as well", e);
                destroy();
            }
        }

        assert ((state == ComponentState.STARTED) == started);
        return started;
    }

    public final boolean stop() {
        if (!init()) {
            return false;
        } else if (getState() != ComponentState.STARTED) {
            return true;
        }

        boolean stopped = true;

        try {
            hooks.doStop();
            state = ComponentState.STOPPED;
        } catch (Exception e) {
            logger.log(Level.WARNING, "Component failed to stop", e);
            stopped = false;
        }

        if (!stopped) {
            destroy();
        }

        assert ((state == ComponentState.STOPPED) == stopped);
        return stopped;
    }

    public void destroy() {
        if (!isInitialized() || isDestroyed()) {
            return;
        }

        if (getState() == ComponentState.STARTED) {
            try {
                hooks.doStop();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to stop", e);
            }
        }

        try {
            hooks.doDestroy();
        } catch (Exception e) {
            logger.log(Level.WARNING, "Component failed to destroy", e);
        }

        state = ComponentState.DESTROYED;
    }

    public final boolean restart() {
        if (!init()) {
            return false;
        }

        boolean failed = false;
        boolean started = true;

        if (getState() == ComponentState.STARTED) {
            try {
                hooks.doStop();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to stop", e);
                failed = true;
            }
        }

        if (!failed) {
            try {
                hooks.doStart();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to re-start", e);
                started = false;
            }
        }

        if (!failed && !started) {
            try {
                hooks.doStop();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Component failed to stop as well", e);
                failed = true;
            }
        }

        if (failed) {
            destroy();
        }

        assert ((state == ComponentState.STARTED) == started);
        assert ((state == ComponentState.DESTROYED) == failed);
        assert ((state == ComponentState.STOPPED) == (!started && !failed));
        return !failed;
    }

    protected void doInit() throws Exception {
        return;
    }

    protected void doStart() throws Exception {
        return;
    }

    protected void doStop() throws Exception {
        return;
    }

    protected void doDestroy() throws Exception {
        return;
    }

    public Object clone() {
        try {
            return (ComponentStateHolder) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.getMessage());
        }
    }

    public ComponentState getState() {
        assert state != null;
        return state;
    }

}

